-- Copyright (c) 2025 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

-- Description: sd500x芯片处理

local skynet = require 'skynet'
local class = require 'mc.class'
local context = require 'mc.context'
local crc8 = require 'mc.crc8'
local log = require 'mc.logging'
local defs = require 'independent_vrd.ind_vrd_defs'
local parse_hex = require 'independent_vrd.tool.parse_hex'

local APP_START = 0x00100000   -- PFlash烧录起始地址
local APP_FILE_LEN = 48 * 1024 -- 48k大小
local PMBUS_READ_HEAD_LEN = 3
local PMBUS_WRITE_HEAD_LEN = 2
local PMBUS_PEC_LEN = 1
local PAGE_BLKHEAD_LEN = 5
local PAGE_BLKDATA_LEN = 16
local CFG_BLOCK_SIZE = 0x1000
local CFG_BLOCK_COUNT = 4
local CFG_FILE_LEN = 16 * 1024
local CFG_TOTAL_FILE_LEN = 32 * 1024
local CFG_BASE_ADDR = 0x70000000
local CFG_BACKUP_ADDR = 0x70004000
local MAX_RETRY = 10

local CMD_ENUM = {
    SAVECFG = 0x11,
    VOUT = 0x21,
    MFRSPEC = 0x80,
    CHIPTEMP = 0x8D,
    POWEROUT = 0x96,
    MFREVERSION = 0x9B,
    DIEID_GET = 0xC0,
    LOADLINER = 0xD5,
    CFGVERSION = 0xD6,
    BOOTROM_SWTICH = 0xD9,
    VBOOT = 0xDF,
    BOOT_VERSION = 0xEC,
    APP_SWTICH = 0xF0,
    PAGE_BLKERASE = 0xF1,
    MASS_FLASH = 0xF2,
    PAGE_BLKWRITE = 0xF4,
    PAGE_BLKREAD = 0xF9,
    PAGE_BLKADDRCFG = 0xFD
}

local sd500x = class()

function sd500x:ctor(tab, position)
    -- tab.object 是原始的VRDFirmware配置对象
    self.obj = tab.object
    self.position = position
end

-- 获取VRD版本信息
function sd500x:get_version()
    local ret, data = self:read_data(self.obj.RefChip, CMD_ENUM.CFGVERSION, 1, self.obj.Address)
    -- 255为异常版本
    if ret ~= defs.RET.OK or not data or data == 255 then
        log:error('[sd500x] Failed to get_version: %s', data)
        return ''
    end
    log:notice('[sd500x] get_version VrdId: %s, data: %s', self.obj.VrdId, string.byte(data, 1))
    return string.byte(data, 1)
end

-- 检查芯片是否可访问
function sd500x:check_chip_accessible()
    log:notice('[sd500x] Checking chip accessibility, UID: %s', self.obj.UID)

    -- 检查第一个芯片
    if not self.obj.RefChip then
        log:error('[sd500x] RefChip not configured')
        return false
    end

    -- 基础固件需要第二个芯片，也需要检查
    if not self.obj.CompRefChip then
        log:error('[sd500x] CompRefChip configured but CompAddress missing')
        return false
    end

    log:notice('[sd500x] Chip accessibility check passed')
    return true
end

-- 切换到Boot ROM模式
function sd500x:switch_to_boot_rom_mode()
    log:notice('[sd500x] Switching to Boot ROM mode, UID: %s', self.obj.UID)
    local cmd = string.char(0x5a, 0x5a)

    local ret = self:write_data(self.obj.RefChip, CMD_ENUM.BOOTROM_SWTICH, cmd, self.obj.Address)
    if ret ~= defs.RET.OK then
        log:error('[sd500x] Failed switched to Boot ROM mode')
        return defs.RET.ERR
    end

    log:notice('[sd500x] Successfully switched to Boot ROM mode')
    return defs.RET.OK
end

function sd500x:enter_boot_rom()
    local ret = self:check_boot_rom_version() -- boot_rom态才查询的到
    if ret == defs.RET.OK then
        return defs.RET.OK
    end

    for _ = 1, MAX_RETRY do
        ret = self:switch_to_boot_rom_mode()
        if ret ~= defs.RET.OK then
            goto continue
        end

        skynet.sleep(10)                    -- 延迟100ms查询
        ret = self:check_boot_rom_version() -- boot_rom态才查询的到,确定切换过去了
        if ret == defs.RET.OK then
            return defs.RET.OK
        end
        ::continue::
    end

    return defs.RET.ERR
end

local function fill_page_head(blklen, blkoffset)
    -- 返回5字节字符串：blklen, off[31:24], off[23:16], off[15:8], len_lo
    local off_31_24 = (blkoffset >> 24) & 0xff
    local off_23_16 = (blkoffset >> 16) & 0xff
    local off_15_8 = (blkoffset >> 8) & 0xff
    local len_lo = blkoffset & 0xff
    return string.char(blklen, off_31_24, off_23_16, off_15_8, len_lo)
end

-- 写入数据到指定分区
function sd500x:write_cfg(config)
    local cnt = config.file_len / PAGE_BLKDATA_LEN
    local ret, offset, head
    for i = 0, cnt - 1 do
        offset = i * PAGE_BLKDATA_LEN
        head = fill_page_head(config.head_len, offset + config.start_addr)
        -- 写入的数据为头+对应偏移的数据
        ret = self:write_data(config.chip, CMD_ENUM.PAGE_BLKWRITE,
            head .. string.sub(config.data, offset + 1, offset + PAGE_BLKDATA_LEN), config.addr)
        if ret ~= defs.RET.OK then
            log:error('[sd500x] write_cfg failed at frame %d (offset 0x%X)', i + 1, offset)
            return defs.RET.ERR
        end
        skynet.sleep(0.5) -- 5ms
    end
    return defs.RET.OK
end

function sd500x:erase_cfg_blocks()
    log:notice('[sd500x] Erasing configuration blocks, UID: %s', self.obj.UID)
    -- 擦除主分区
    for idx = 0, CFG_BLOCK_COUNT - 1 do
        local offset = CFG_BASE_ADDR + CFG_BLOCK_SIZE * idx
        local head = fill_page_head(PAGE_BLKHEAD_LEN - 1, offset)
        local ret = self:write_data(self.obj.RefChip, CMD_ENUM.PAGE_BLKERASE, head, self.obj.Address)
        if ret ~= defs.RET.OK then
            log:error('[sd500x] Erase main config block %d failed at offset 0x%X', idx, offset)
            return defs.RET.ERR
        end
    end
    -- 擦除备份区
    for idx = 0, CFG_BLOCK_COUNT - 1 do
        local offset = CFG_BACKUP_ADDR + CFG_BLOCK_SIZE * idx
        local head = fill_page_head(PAGE_BLKHEAD_LEN - 1, offset)
        local ret = self:write_data(self.obj.RefChip, CMD_ENUM.PAGE_BLKERASE, head, self.obj.Address)
        if ret ~= defs.RET.OK then
            log:error('[sd500x] Erase backup config block %d failed at offset 0x%X', idx, offset)
            return defs.RET.ERR
        end
    end
    return defs.RET.OK
end

-- 配置升级流程
-- 1. 解析配置文件并做尺寸校验
-- 2. 擦除主分区与备份区
-- 3. 写入并验证主分区
-- 4. 写入并验证备份区
function sd500x:upgrade_cfg(dir)
    log:notice('[sd500x] ===== Start configuration upgrade =====')
    if self.obj.UpgradeFileName.FirmwareFileName == '' then
        log:error('[sd500x][CFG] cfgpath not set')
        return defs.RET.ERR
    end
    -- 1. 解析配置文件阶段
    local cfg_path = dir .. self.obj.UpgradeFileName.FirmwareFileName
    local cfg_data = parse_hex(cfg_path, CFG_TOTAL_FILE_LEN)
    if not cfg_data or cfg_data.ret ~= defs.RET.OK or cfg_data.real_len < CFG_TOTAL_FILE_LEN then
        log:error('[sd500x][CFG] Parse cfg file failed, path=%s', cfg_path)
        return defs.RET.ERR
    end

    -- 2. 擦除块阶段
    local ret = self:erase_cfg_blocks()
    if ret ~= defs.RET.OK then
        log:error('[sd500x][CFG] Erase blocks failed')
        return defs.RET.ERR
    end

    -- 3. 主分区写入和验证阶段
    -- 主分区：前16k
    local main_partition = string.sub(cfg_data.buffer, 1, CFG_FILE_LEN)
    ret = self:write_file_data({
        data = main_partition,
        start_addr = CFG_BASE_ADDR,
        file_len = CFG_FILE_LEN,
        head_len = PAGE_BLKHEAD_LEN + PAGE_BLKDATA_LEN - 1,
        chip = self.obj.RefChip,
        addr = self.obj.Address
    })
    if ret ~= defs.RET.OK then
        log:error('[sd500x][CFG] Write and verify main partition failed')
        return defs.RET.ERR
    end

    -- 4. 备份区写入和验证阶段
    -- 备份区：后16k
    local backup_partition = string.sub(cfg_data.buffer, CFG_FILE_LEN + 1, CFG_TOTAL_FILE_LEN)
    ret = self:write_file_data({
        data = backup_partition,
        start_addr = CFG_BACKUP_ADDR,
        file_len = CFG_FILE_LEN,
        head_len = PAGE_BLKHEAD_LEN + PAGE_BLKDATA_LEN - 1,
        chip = self.obj.RefChip,
        addr = self.obj.Address
    })
    if ret ~= defs.RET.OK then
        log:error('[sd500x][CFG] Write and verify backup partition failed')
        return defs.RET.ERR
    end

    log:notice('[sd500x] ===== Configuration upgrade completed successfully =====')
    return defs.RET.OK
end

-- 从指定分区读取数据
function sd500x:read_cfg(config)
    local cnt = config.file_len / PAGE_BLKDATA_LEN
    local parts = {}
    local ret, offset, head, payload
    for i = 0, cnt - 1 do
        offset = i * PAGE_BLKDATA_LEN
        -- 仅配置地址，头长度为5字节
        head = fill_page_head(PAGE_BLKHEAD_LEN - 1, offset + config.start_addr)
        ret = self:write_data(config.chip, CMD_ENUM.PAGE_BLKADDRCFG, head, config.addr)
        if ret == defs.RET.OK then
            -- 读取16字节（返回包含 PEC，截取前16字节）
            ret, payload = self:read_data(config.chip, CMD_ENUM.PAGE_BLKREAD, PAGE_BLKDATA_LEN + 1, config.addr)
        end
        if ret == defs.RET.OK and payload then
            parts[#parts + 1] = string.sub(payload, 2, PAGE_BLKDATA_LEN + 1)
        end
        if ret ~= defs.RET.OK then
            break
        end
    end
    return ret, table.concat(parts)
end

-- 验证指定分区的数据（只读验证，不写入）
function sd500x:verify_data(config)
    local file_len = config.file_len
    local read_ret, data = self:read_cfg(config)
    if read_ret ~= defs.RET.OK then
        return defs.RET.ERR
    end
    if not data or #data < file_len then
        log:error('[sd500x] verify length mismatch, expect=%d, got=%d', file_len, #data)
        return defs.RET.ERR
    end
    -- 按字节对比
    for i = 1, file_len do
        if string.byte(config.data, i) ~= string.byte(data, i) then
            log:error('[sd500x] verify mismatch at idx=%s, except: %s, actual: %s',
                i, string.byte(config.data, i), string.byte(data, i))
            return defs.RET.ERR
        end
    end
    return defs.RET.OK
end

-- 写入文件数据并验证（透传配置表给下层函数）
function sd500x:write_file_data(config)
    log:notice('[sd500x] write_file_data start, start_addr=0x%X, file_len=%d', config.start_addr, config.file_len)

    local ret = self:write_cfg(config)
    if ret ~= defs.RET.OK then
        log:error('[sd500x] write_file_data write failed, start_addr=0x%X', config.start_addr)
        return defs.RET.ERR
    end
    log:notice('[sd500x] write_file_data write completed, start_addr=0x%X', config.start_addr)

    -- 使用验证函数进行回读验证
    log:notice('[sd500x] write_file_data verify start, start_addr=0x%X', config.start_addr)
    ret = self:verify_data(config)
    if ret ~= defs.RET.OK then
        log:error('[sd500x] write_file_data verify failed, start_addr=0x%X', config.start_addr)
        return defs.RET.ERR
    end
    log:notice('[sd500x] write_file_data verify completed, start_addr=0x%X', config.start_addr)

    log:notice('[sd500x] write_file_data success, start_addr=0x%X, file_len=%d', config.start_addr, config.file_len)
    return defs.RET.OK
end

-- 烧录固件到pflash
function sd500x:write_firmware_to_flash(data)
    log:notice('[sd500x] Writing firmware to flash, UID: %s', self.obj.UID)
    local ret
    for _ = 1, MAX_RETRY do
        ret = self:write_data(self.obj.CompRefChip, CMD_ENUM.MASS_FLASH, string.char(1), self.obj.CompAddress) -- 1代表擦除Pflash
        if ret ~= defs.RET.OK then
            goto continue
        end
        skynet.sleep(10)

        ret = self:write_file_data({
            data = data,
            start_addr = APP_START,
            file_len = APP_FILE_LEN,
            head_len = PAGE_BLKHEAD_LEN + PAGE_BLKDATA_LEN - 1,
            chip = self.obj.CompRefChip,
            addr = self.obj.CompAddress
        })
        if ret == defs.RET.OK then
            log:notice('[sd500x] Firmware write completed')
            return defs.RET.OK
        end
        skynet.sleep(20)
        ::continue::
    end
    log:error('[sd500x] Firmware write failed')
    return defs.RET.ERR
end

-- 切换到APP模式
function sd500x:switch_to_app_mode()
    log:notice('[sd500x] Switching to APP mode, UID: %s', self.obj.UID)

    local ret = self:write_data(self.obj.CompRefChip, CMD_ENUM.APP_SWTICH, string.char(0), self.obj.CompAddress)
    if ret ~= defs.RET.OK then
        return defs.RET.ERR
    end

    log:notice('[sd500x] Successfully switched to APP mode')
    return defs.RET.OK
end

function sd500x:get_app_die_id()
    local ret, data = self:read_data(self.obj.RefChip, CMD_ENUM.DIEID_GET, 21, self.obj.Address)
    if ret ~= defs.RET.OK then
        log:error('[sd500x] failed get_app_die_id')
        return defs.RET.ERR
    end
    if not data or #data < 21 then
        log:error('[sd500x] get_app_die_id: insufficient data, got %d bytes', data and #data or 0)
        return defs.RET.ERR
    end

    -- 第一个字节表示后续字节的长度
    local die_id_len = string.byte(data, 1)
    if die_id_len < 1 or die_id_len > 20 then
        log:error('[sd500x] get_app_die_id: invalid die_id length %d', die_id_len)
        return defs.RET.ERR
    end

    -- 从第2个字节开始，读取die_id_len个字节作为die_id
    local die_id_data = string.sub(data, 2, 1 + die_id_len)
    local hex_parts = {}
    for i = 1, #die_id_data do
        hex_parts[i] = string.format('%02X', string.byte(die_id_data, i))
    end
    local hex_str = table.concat(hex_parts)
    log:notice('[sd500x] get app_die_id: %s', hex_str)
    return defs.RET.OK
end

-- 验证升级后的固件版本
function sd500x:read_fw_version()
    log:notice('[sd500x] Verifying upgrade version, UID: %s', self.obj.UID)

    local ret, data = self:read_data(self.obj.RefChip, CMD_ENUM.MFREVERSION, 5, self.obj.Address)
    if ret ~= defs.RET.OK or not data then
        log:error('[sd500x] read_fw_version failed')
        return defs.RET.ERR
    end
    log:maintenance(log.MLOG_INFO, log.FC__PUBLIC_OK,
        'Vrd(%s) mfr_version=%s%s%s%s', self.obj.VrdId, string.byte(data, 2), string.byte(data, 3),
        string.byte(data, 4), string.byte(data, 5))
    log:notice('[sd500x] Vrd(%s) version=%s%s%s%s', self.obj.VrdId, string.byte(data, 2), string.byte(data, 3),
        string.byte(data, 4), string.byte(data, 5))

    return defs.RET.OK
end

-- 基础固件升级流程
-- 1. 芯片检测
-- 2. 升级包解析
-- 3. Boot ROM模式切换
-- 4. 固件烧录
-- 5. APP模式切换
-- 6. 获取 die id 并验证版本
function sd500x:upgrade_firmware(dir)
    log:notice('[sd500x] ===== Start firmware upgrade =====')
    -- 1. 芯片检测阶段
    if not self:check_chip_accessible() then
        log:error('[sd500x] Chip accessibility check failed')
        return defs.RET.ERR
    end

    -- 2. 升级包解析阶段
    if self.obj.UpgradeFileName.BootFileName == '' then
        log:error('[sd500x] not find BootFileName')
        return defs.RET.ERR
    end
    local hex_file = dir .. self.obj.UpgradeFileName.BootFileName
    local data = parse_hex(hex_file, APP_FILE_LEN)
    if data.ret ~= defs.RET.OK or data.real_len < APP_FILE_LEN then
        log:error('[sd500x] Failed to parse HEX file')
        return defs.RET.ERR
    end

    -- 3. Boot ROM阶段
    local ret = self:enter_boot_rom()
    if ret ~= defs.RET.OK then
        log:error('[sd500x] Failed to switch to Boot Rom')
        return defs.RET.ERR
    end

    -- 4. 固件烧录阶段
    ret = self:write_firmware_to_flash(data.buffer)
    if ret ~= defs.RET.OK then
        log:error('[sd500x] Failed to write firmware to flash')
        return defs.RET.ERR
    end
    skynet.sleep(10)

    -- 5. APP模式切换阶段
    ret = self:switch_to_app_mode()
    if ret ~= defs.RET.OK then
        log:error('[sd500x] Failed to switch to APP mode')
        return defs.RET.ERR
    end
    skynet.sleep(10)

    -- 6. 获取dieid和版本验证
    ret = self:get_app_die_id()
    if ret ~= defs.RET.OK then
        return defs.RET.ERR
    end

    self:read_fw_version()

    log:notice('[sd500x] ===== Firmware upgrade completed successfully =====')
    return defs.RET.OK
end

-- 主升级接口：先尝试固件升级（失败会记录日志但不中断），随后强制执行配置升级
function sd500x:upgrade(dir)
    log:notice('[sd500x] ===== Start VRD upgrade =====')
    -- 基础固件升级, 失败也继续执行配置升级
    local ret = self:upgrade_firmware(dir)
    if ret ~= defs.RET.OK then
        log:error('[sd500x] upgrade_firmware failed')
    end

    -- 配置升级
    ret = self:upgrade_cfg(dir)
    if ret ~= defs.RET.OK then
        return defs.RET.ERR
    end

    log:notice('[sd500x] ===== VRD upgrade completed successfully =====')
    return defs.RET.OK
end

-- 写寄存器让VRD立即生效
function sd500x:valid_vrd()
    local ok, err = pcall(function()
            self.obj.ValidateReg = 1
        end)
    if not ok then
        log:error('[sd500x] Set vrd vaild failed, err: %s', err)
        return defs.RET.ERR
    end
    log:notice('[sd500x] Set vrd vaild successfully')
    return defs.RET.OK
end

-- 版本格式0x0001000n (n=1-9)
function sd500x:check_boot_rom_version()
    local ret, data = self:read_data(self.obj.CompRefChip, CMD_ENUM.BOOT_VERSION, 5, self.obj.CompAddress)
    if ret ~= defs.RET.OK or not data then
        return defs.RET.ERR
    end
    log:notice('boot rom version: 0x%02x%02x%02x%02x', string.byte(data, 2),
        string.byte(data, 3), string.byte(data, 4), string.byte(data, 5))
    if string.byte(data, 2) == 0 and string.byte(data, 3) == 1 and
        string.byte(data, 4) == 0 and string.byte(data, 5) >= 1 and string.byte(data, 5) <= 9 then
        return defs.RET.OK
    end
    return defs.RET.ERR
end

-- 总长度datalen + PMBUS_READ_HEAD_LEN + PMBUS_PEC_LEN
function sd500x:read_data(chip, cmd, datalen, addr)
    -- 头三个字节：addr, cmd, 第一个字节最低位置1
    local head = string.char(addr, cmd, addr | 1)

    for _ = 1, MAX_RETRY do
        local ok, payload = pcall(function()
            return chip:Read(context.get_context_or_default(), cmd, datalen + PMBUS_PEC_LEN)
        end)
        if not ok or not payload or #payload < (datalen + PMBUS_PEC_LEN) then
            log:error('[sd500x] Read failed due to insufficient data/errors')
            goto continue
        end

        -- 拼接完整码流
        local full_stream = head .. payload
        -- 计算CRC
        local crc = crc8(string.sub(full_stream, 1, datalen + PMBUS_READ_HEAD_LEN))
        local expected_crc = string.byte(full_stream, datalen + PMBUS_READ_HEAD_LEN + PMBUS_PEC_LEN)
        if expected_crc == crc then
            -- crc校验
            return defs.RET.OK, string.sub(payload, 1, datalen)
        end
        ::continue::
    end

    log:error('[sd500x] Read cmd:0x%02X failed after %d retries', cmd, MAX_RETRY)
    return defs.RET.ERR
end

-- 总长度datalen + PMBUS_WRITE_HEAD_LEN + PMBUS_PEC_LEN
function sd500x:write_data(chip, cmd, data, addr)
    -- 头三个字节：addr, cmd
    local head = string.char(addr, cmd)
    local write_data = data .. string.char(crc8(head .. data))
    local ok, payload
    for _ = 1, MAX_RETRY do
        ok, payload = pcall(function()
            return chip:Write(context.get_context_or_default(), cmd, write_data)
        end)
        if ok then
            return defs.RET.OK
        end
        skynet.sleep(1) -- 10ms
    end

    log:error('[sd500x] Write failed, cmd:%s, err: %s', cmd, payload)
    return defs.RET.ERR
end

return sd500x